package main

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/androidsr/sc-go/sgorm"
	"github.com/bwmarrin/snowflake"
	uuid "github.com/satori/go.uuid"

	"github.com/androidsr/sc-go/syaml"
	"gopkg.in/yaml.v3"
)

type WriteConfig struct {
	Spec string      `yaml:"spec"`
	Name string      `yaml:"name"`
	Url  string      `yaml:"url"`
	Sqls []WriteItem `yaml:"sqls"`
}

type WriteItem struct {
	SingleSize int    `yaml:"single-size"`
	Interval   int    `yaml:"interval"`
	Repeated   string `yaml:"repeated"`
	Sql        string `yaml:"sql"`
}

type ReadUrls struct {
	Name string              `yaml:"name"`
	Url  string              `yaml:"url"`
	Sqls []map[string]string `yaml:"sqls"`
}

type Config struct {
	WriteUrls  []*WriteConfig `yaml:"write-urls"`
	ReadUrls   []*ReadUrls    `yaml:"read-urls"`
	SingleSize int            `yaml:"single-size"`
}

var (
	config       Config
	writeConnect = make(map[string]*sgorm.Sgorm, 0)
	readConnect  = make(map[string]*sgorm.Sgorm, 0)
	nid          *snowflake.Node
	localFileMap = make(map[string]*bytes.Buffer, 0)
)

func main() {
	log.SetFlags(log.Llongfile | log.LstdFlags)
	data, err := os.ReadFile("sc-go.yaml")
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		panic(err)
	}
	nid, _ = snowflake.NewNode(12)

	fmt.Println("初始化读")
	for _, r := range config.ReadUrls {
		readConnect[r.Name] = sgorm.New(&syaml.GormInfo{
			Driver:  "mysql",
			Url:     r.Url,
			MaxOpen: 3,
			MaxIdle: 3,
			ShowSql: true,
		})
	}
	for _, w := range config.WriteUrls {
		writeConnect[w.Name] = sgorm.New(&syaml.GormInfo{
			Driver:  "mysql",
			Url:     w.Url,
			MaxOpen: 3,
			MaxIdle: 3,
			ShowSql: false,
		})
	}
	execTask()
	select {}
}

func execTask() {
	for _, w := range config.WriteUrls {
		for _, item := range w.Sqls {
			go func(dbCfg WriteConfig, cfg WriteItem) {
				ticker := time.NewTicker(time.Duration(cfg.Interval) * time.Second)
				for range ticker.C {
					max := rand.Intn(cfg.SingleSize)
					if max == 0 {
						max = 1
					}
					i := rand.Intn(max)
					if i == 0 {
						i = 1
					}
					for i > 0 {
						time.Sleep(time.Duration(cfg.Interval))
						readSqlResultMap := make(map[string]map[string]interface{}, 0)
						newSql, checkSql, err := replaceSql(cfg.Sql, cfg.Repeated, readSqlResultMap)
						if err != nil {
							fmt.Println(err)
							continue
						}
						db := writeConnect[dbCfg.Name]
						var count int
						if checkSql != "" {
							err = db.DB.Raw(checkSql).Scan(&count).Error
							if err != nil {
								fmt.Println(err)
								continue
							}
							if count > 0 {
								continue
							}
						}
						err = db.Exec(newSql).Error
						if err != nil {
							fmt.Println(err.Error())
						}
						i--
					}

				}
			}(*w, item)
		}
	}
}

func replaceSql(sql string, query string, readSqlResultMap map[string]map[string]interface{}) (string, string, error) {
	re := regexp.MustCompile(`\$[-_:a-zA-Z0-9.]+`)
	results := re.FindAllString(sql, -1)
	if len(results) > 0 {
		for _, v := range results {
			if v == "$uuid" {
				uId := uuid.NewV4()
				value := uId.String()
				sql = strings.Replace(sql, v, value, 1)
				if query != "" {
					query = strings.Replace(query, v, value, 1)
				}
			} else if v == "$id" {
				value := nid.Generate().String()
				sql = strings.Replace(sql, v, value, 1)
				if query != "" {
					query = strings.Replace(query, v, value, 1)
				}
			} else if v == "$date" {
				value := time.Now().Format("2006-01-02")
				sql = strings.Replace(sql, v, value, 1)
				if query != "" {
					query = strings.Replace(query, v, value, 1)
				}
			} else if v == "$datetime" {
				value := time.Now().Format("2006-01-02 15:04:05")
				sql = strings.Replace(sql, v, value, 1)
				if query != "" {
					query = strings.Replace(query, v, value, 1)
				}
			} else if strings.HasPrefix(v, "$local:") {
				filename := strings.ReplaceAll(v, "$local:", "")
				if localFileMap[filename] == nil {
					data, err := os.ReadFile(filename)
					if err != nil {
						return "", "", err
					}
					localFileMap[filename] = bytes.NewBuffer(data)
				}
				data := localFileMap[filename].Bytes()
				buf := bytes.NewBuffer(data)
				max := len(strings.Split(string(data), "\n"))
				randomNumber := rand.Intn(max) + 1
				var line string
				var err error
				for i := 0; i < randomNumber; i++ {
					line, err = buf.ReadString('\n')
					if err != nil {
						return "", "", err
					}
				}
				sql = strings.Replace(sql, v, strings.TrimSpace(line), 1)
				if query != "" {
					query = strings.Replace(query, v, strings.TrimSpace(line), 1)
				}
			} else if strings.HasPrefix(v, "$number:") {
				maxStr := strings.ReplaceAll(v, "$number:", "")
				var err error
				var min int64 = 0
				if strings.Contains(maxStr, "-") {
					ix := strings.Split(maxStr, "-")
					maxStr = ix[1]
					min, err = strconv.ParseInt(ix[0], 10, 64)
					if err != nil {
						return "", "", err
					}
				}
				max, err := strconv.ParseInt(maxStr, 10, 64)
				if err != nil {
					return "", "", err
				}
				randomNumber := rand.Intn(int(max))
				value := strings.TrimSpace(fmt.Sprint(randomNumber + int(min)))
				sql = strings.Replace(sql, v, value, 1)
				if query != "" {
					query = strings.Replace(query, v, value, 1)
				}
			} else if strings.Contains(v, ".") {
				db, fSql, key, err := getReadSql(v)
				if err != nil {
					return "", "", err
				}

				fSql, _, err = replaceSql(fSql, "", readSqlResultMap)
				if err != nil {
					return "", "", err
				}

				resultKey := v[:strings.LastIndex(v, ".")]
				var resultMap = readSqlResultMap[resultKey]
				if resultMap == nil {
					resultMap = make(map[string]interface{}, 0)
				}
				err = db.Raw(fSql).Scan(&resultMap).Error
				if err != nil {
					return "", "", err
				}
				value := fmt.Sprint(resultMap[key])
				sql = strings.Replace(sql, v, value, 1)
				if query != "" {
					query = strings.Replace(query, v, value, 1)
				}
				readSqlResultMap[resultKey] = resultMap
			}
		}
	}
	return sql, query, nil
}

func getReadSql(name string) (*sgorm.Sgorm, string, string, error) {
	dn := strings.Split(strings.ReplaceAll(name, "$", ""), ".")
	dbname := dn[0]
	sqlName := dn[1]
	for _, v := range config.ReadUrls {
		fmt.Println(v)
		if v.Name == strings.TrimSpace(dbname) {
			for _, s := range v.Sqls {
				if s["name"] == sqlName {
					return readConnect[v.Name], s["sql"], dn[2], nil
				}
			}
		}
	}
	return nil, "", "", errors.New("未找到")
}
